package com.zmj.sy.mom.srv.aps.service.scheduler.inverted;

import com.zmj.sy.mom.srv.aps.bean.entity.order.OrderBom;
import com.zmj.sy.mom.srv.aps.bean.entity.order.OrderPhase;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Group;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Phase;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Shift;
import com.zmj.sy.mom.srv.aps.bean.entity.resource.Station;
import com.zmj.sy.mom.srv.aps.service.scheduler.order.DefaultPhase;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.DefaultCalendar;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.DefaultFactory;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.shift.MultiTimeRange;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.shift.ShiftTypeDo;
import com.zmj.sy.mom.srv.aps.service.scheduler.resource.shift.TimeUtils;
import lombok.Getter;
import lombok.Setter;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;

/**
 * 排产资源数据
 */
@Getter
@Setter
public class InvertedScheduleResource {

    private DefaultFactory factory;

    private DefaultCalendar calendar;

    /**
     * 判断某天是否上班
     */
    public boolean validDate(InvertedScheduleContext context,
                             OrderBom bom,
                             OrderPhase phase,
                             LocalDate date) {
        if (phase == null || phase.getWorkTime() < 2) {
            return InvertedScheduleContext.NO_SHIFT_STRATEGY_VALID.equals(context.getNoShiftStrategy());
        }
        Group group = factory.group(phase.getWorkGroupNumber());
        Assert.notNull(group, "工作组" + phase.getWorkGroupNumber() + "不存在");
        List<Station> stations = factory.stations(group.getId());
        return !calendar.workShifts(group, stations,  date).isEmpty();
    }

    /**
     * 获取某时间点前上班的时间
     */
    public LocalDateTime previousValidTime(InvertedScheduleContext context,
                                           OrderBom bom,
                                           OrderPhase phase,
                                           LocalDateTime dateTime) {
        int limit = 365;
        LocalDate startDate = dateTime.toLocalDate();
        LocalTime startTime = dateTime.toLocalTime();
        while (limit-- > 0) {
            if (validDate(context, bom, phase, startDate)) {
                return startDate.atTime(startTime);
            }
            startDate = startDate.minusDays(1);
            startTime = LocalTime.MAX;
        }
        throw new IllegalArgumentException("日历异常，排产时间超出限制");
    }

    /**
     * 获取某时间点后上班的时间
     */
    public LocalDateTime nextValidTime(InvertedScheduleContext context,
                                       OrderBom bom,
                                       OrderPhase phase,
                                       LocalDateTime dateTime) {
        int limit = 365;
        LocalDate startDate = dateTime.toLocalDate();
        LocalTime startTime = dateTime.toLocalTime();
        while (limit-- > 0) {
            if (validDate(context, bom, phase, startDate)) {
                return startDate.atTime(startTime);
            }
            startDate = startDate.plusDays(1);
            startTime = LocalTime.MAX;
        }
        throw new IllegalArgumentException("日历异常，排产时间超出限制");
    }

    /**
     * 计算转运工时（转入+转出）
     */
    public Duration transferTime(DefaultPhase phase) {
        String workGroupNumber = phase.getPhase().getWorkGroupNumber();
        Group group = factory.group(workGroupNumber);
        Assert.notNull(group, "工作组" + workGroupNumber + "不存在");
        List<Station> stations = factory.stations(group.getId());
        Assert.notEmpty(stations, "工作组" + workGroupNumber + "没有工位");
        Integer transferIn = stations.get(0).getEnterTime();
        if (transferIn == null) {
            transferIn = 0;
        }
        Integer transferOut = stations.get(0).getOutTime();
        if (transferOut == null) {
            transferOut = 0;
        }
        return Duration.ofSeconds(transferIn + transferOut);
    }

    public List<Shift> workShifts(DefaultPhase phase, LocalDate date) {
        Group group = factory.group(phase.getPhase().getWorkGroupNumber());
        List<Station> stations = factory.stations(group.getId());
        return calendar.workShifts(group, stations, date);
    }

    public List<Shift> shifts(DefaultPhase phase) {
        Group group = factory.group(phase.getPhase().getWorkGroupNumber());
        List<Station> stations = factory.stations(group.getId());
        return calendar.shifts(group, stations);
    }

    public MultiTimeRange workTime(DefaultPhase phase, LocalDate date, Shift  shift) {
        Group group = factory.group(phase.getPhase().getWorkGroupNumber());
        List<Station> stations = factory.stations(group.getId());
        Integer shiftTypeId = group.getShiftTypeId();
        if (shiftTypeId == null) {
            shiftTypeId = stations.get(0).getShiftTypeId();
        }
        ShiftTypeDo shiftTypeDo = calendar.shiftTypeDo(shiftTypeId);
        Assert.notNull(shiftTypeDo, "工作组" + group.getName() + "没有配置班制");
        return shiftTypeDo.workTime(shift, date);
    }

    public Duration cacheTime(DefaultPhase phase) {
        Phase stationPhase = factory.phase(phase.getPhase().getPhaseCode());
        if (stationPhase == null) {
            return Duration.ZERO;
        }
        return TimeUtils.time(stationPhase.getCacheTimeValue(), stationPhase.getCacheTimeUnit());
    }

    public BigDecimal workAbility(DefaultPhase phase) {
        Group group = factory.group(phase.getPhase().getWorkGroupNumber());
        if (group == null) {
            return BigDecimal.ONE;
        }
        List<Station> stations = factory.stations(group.getId());
        Integer shiftTypeId = group.getShiftTypeId();
        if (shiftTypeId == null) {
            shiftTypeId = stations.get(0).getShiftTypeId();
        }
        ShiftTypeDo shiftTypeDo = calendar.shiftTypeDo(shiftTypeId);
        if (shiftTypeDo == null) {
            return BigDecimal.ONE;
        }
        return shiftTypeDo.workAbility();
    }

}
